﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace SimplexEngine
{
    public class RenderElement : IComparable<RenderElement>
    {
        public RenderElement(IMeshBuffer element_mesh_buffer, Material element_material, SceneObject element_scene_object)
        {
            material = element_material;
            mesh_buffer = element_mesh_buffer;
            scene_object = element_scene_object;
        }

        public SceneObject SceneObject { get { return scene_object; } }
        protected SceneObject scene_object;
        protected Material material;
        public Material Material { get { return material; } }
        protected IMeshBuffer mesh_buffer;
        public IMeshBuffer MeshBuffer { get { return mesh_buffer; } }

        public int CompareTo(RenderElement other)
        {
            if (material.Effect == other.material.Effect)
                return material.TechniqueName.CompareTo(other.material.TechniqueName);

            return material.Effect.GetHashCode().CompareTo(other.material.Effect.GetHashCode());
        }
    }

    public class RenderSystem
    {
        public const int MAX_BONE_COUNT = 60;

        public RenderSystem()
        {
            graphics_device_manager = new GraphicsDeviceManager(Core.Game);
            graphics_device_manager.PreferredBackBufferWidth = 1280;
            graphics_device_manager.PreferredBackBufferHeight = 720;
            graphics_device_manager.PreparingDeviceSettings += new EventHandler<PreparingDeviceSettingsEventArgs>(PreparingDeviceSettings);
            graphics_device_manager.DeviceCreated += new EventHandler(DeviceCreated);
        }

        void PreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)
        {
            
        }

        private void DeviceCreated(object sender, EventArgs e)
        {
            sprite_batch = new SpriteBatch(GraphicsDevice);

            default_view_port = graphics_device_manager.GraphicsDevice.Viewport;
            // mrt
            default_mrt_context = CreateRenderContext("MRT", "MRT", 200);
            // lighting
            default_lighting_context = CreateRenderContext("Lighting", "Texture", 100);
            default_lighting_context.ViewFilter = (int)SceneObjectFilter.LIGHT;
            default_lighting_context.ClearOptions = ClearOptions.Target;
            Ambient = new Color(0.2f, 0.2f, 0.2f);
            // create default render context
            default_render_context = CreateRenderContext("BackBuffer", "BackBuffer", 0);
            // create vertex declaration
            CreateVertexDeclaration();
        }
        protected GraphicsDeviceManager graphics_device_manager = null;

        public SpriteManager SpriteManager { get { return default_render_context.SpriteManager; } }

        SpriteBatch sprite_batch = null;
        public SpriteBatch SpriteBatch { get { return sprite_batch; } }

        RenderContext default_render_context = null;
        public RenderContext DefaultRenderContext { get { return default_render_context; } }

        RenderContext default_mrt_context = null;
        public RenderContext DefaultMRTContext { get { return default_mrt_context; } }

        RenderContext default_lighting_context = null;
        public RenderContext DefaultLightingContext { get { return default_lighting_context; } }

        internal GraphicsDevice GraphicsDevice { get { return graphics_device_manager.GraphicsDevice; } }

        protected Viewport default_view_port;
        public Viewport DefaultViewPort { get { return default_view_port; } }

        public RenderContext CreateRenderContext(String name, String type, int priority)
        {
            RenderContext render_context = null;

            // TODO : create a render context factory
            if (type == "MRT")
                render_context = new MRTRenderContext(name, this);
            else if (type == "BackBuffer")
                render_context = new BackBufferRenderContext(name, this);
            else if (type == "Texture")
                render_context = new TextureRenderContext(name, this);

            if (render_context != null)
            {
                render_context.Priority = priority;

                AddRenderContext(render_context);
            }

            return render_context;
        }

        public RenderContext GetRenderContext(String name)
        {
            return render_context_map[name];
        }

        public void AddRenderContext(RenderContext render_context)
        {
            render_context_map.Add(render_context.Name, render_context);
            render_contexts.Add(render_context);
            // sort by priority
            render_contexts.Sort();
            // desc
            render_contexts.Reverse();
        }

        public void RemoveRenderContext(String name)
        {
            RenderContext render_context = render_context_map[name];
            render_context_map.Remove(name);
            render_contexts.Remove(render_context);
        }

        public void Draw(GameTime game_time, Int64 frame_number)
        {
            // render all render contexts, order by priority
            foreach (RenderContext render_context in render_contexts)
            {
                solid_render_list.Clear();
                transparency_render_list.Clear();
                render_context.Draw(game_time, frame_number);
                RenderList(game_time, frame_number, solid_render_list);
                RenderList(game_time, frame_number, transparency_render_list);
            }
        }

        public Color Ambient 
        {
            get { return default_lighting_context.ClearColor; }
            set { default_lighting_context.ClearColor = value; }
        }

        public class BasicRenderParameters
        {
            public Matrix World;
            public Matrix View;
            public Matrix Projection;
        }

        public BasicRenderParameters RenderParameters
        {
            get { return basic_render_parameters; }
        }
        protected BasicRenderParameters basic_render_parameters = new BasicRenderParameters();

        private void RenderList(GameTime game_time, Int64 frame_number, List<RenderElement> render_list)
        {
            render_list.Sort();
            Material last_material = null;

            foreach (RenderElement render_element in render_list)
            {
                if (render_element.Material.Effect == null)
                    continue;

                basic_render_parameters.World = render_element.SceneObject.Parent.Transform;                

                if (last_material == null || last_material.Effect != render_element.Material.Effect || last_material.TechniqueName != render_element.Material.TechniqueName)
                {
                    if (last_material != null && last_material.Effect != null)
                        last_material.Effect.End();
                    last_material = render_element.Material;
                    render_element.Material.ChangeTechnique();
                    render_element.Material.Effect.Begin();
                }

                render_element.Material.ApplyParameters(game_time, frame_number);

                render_element.Material.Effect.CommitChanges();

                foreach (EffectPass effect_pass in render_element.Material.Effect.CurrentTechnique.Passes)
                {
                    effect_pass.Begin();
                    DrawMeshBuffer(render_element.MeshBuffer);
                    effect_pass.End();
                }
            }

            if (last_material != null && last_material.Effect != null)
                last_material.Effect.End();
        }

        private void DrawMeshBuffer(IMeshBuffer mesh_buffer)
        {
            HardwareMeshBuffer hardware_mesh_buffer = hardware_mesh_buffer_manager.GetHardwareMeshBuffer(mesh_buffer);
            if (hardware_mesh_buffer != null)
            {
                DrawHardwareMeshBuffer(mesh_buffer, hardware_mesh_buffer);
            }
            else
            {
                GraphicsDevice.VertexDeclaration = vertex_declaration;
                GraphicsDevice.DrawUserIndexedPrimitives<Vertex>(PrimitiveType.TriangleList, mesh_buffer.GetVertices(), 0, (int)mesh_buffer.GetVertexCount(), mesh_buffer.GetIndices(), 0, (int)mesh_buffer.GetIndexCount() / 3);
            }

        }

        private void DrawHardwareMeshBuffer(IMeshBuffer mesh_buffer, HardwareMeshBuffer hardware_mesh_buffer)
        {
            GraphicsDevice.VertexDeclaration = vertex_declaration;
            GraphicsDevice.Vertices[0].SetSource(hardware_mesh_buffer.VertexBuffer, 0, Vertex.SizeInBytes);
            GraphicsDevice.Indices = hardware_mesh_buffer.IndefBuffer;
            GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, (int)mesh_buffer.GetVertexCount(), 0, (int)mesh_buffer.GetIndexCount() / 3); 
        }

        public void AddToRenderList(RenderElement render_element)
        {
            if (render_element.Material != null && render_element.Material.IsTransparency)
                AddToTransparencyRenderList(render_element);
            else
                AddToSolidRenderList(render_element);
        }

        public void AddToSolidRenderList(RenderElement render_element)
        {
            solid_render_list.Add(render_element);
        }

        public void AddToTransparencyRenderList(RenderElement render_element)
        {
            transparency_render_list.Add(render_element);
        }

        private List<RenderElement> solid_render_list = new List<RenderElement>();
        private List<RenderElement> transparency_render_list = new List<RenderElement>();

        protected int last_render_target_count = 0;

        public void SetRenderContext(RenderContext render_context)
        {
            for (int i = 0; i < render_context.GetRenderTargets().Count; ++i)
            {
                GraphicsDevice.SetRenderTarget(i, render_context.GetRenderTargets()[i]);
            }            

            for (int i = render_context.GetRenderTargets().Count; i < last_render_target_count; ++i)
                GraphicsDevice.SetRenderTarget(i, null);

            GraphicsDevice.Clear(render_context.ClearOptions, render_context.ClearColor, 1.0f, 0);            

            last_render_target_count = render_context.GetRenderTargets().Count;
        }

        public Viewport ViewPort { get { return graphics_device_manager.GraphicsDevice.Viewport; } set { graphics_device_manager.GraphicsDevice.Viewport = value; } }

        protected List<RenderContext> render_contexts = new List<RenderContext>();
        protected Dictionary<String, RenderContext> render_context_map = new Dictionary<String, RenderContext>();

        private void CreateVertexDeclaration()
        {
            VertexElement[] elements = new VertexElement[]
            {
                new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0),
                new VertexElement(0, 12, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0),
                new VertexElement(0, 24, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Tangent, 0),
                new VertexElement(0, 36, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Binormal, 0),
                new VertexElement(0, 48, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0),
                new VertexElement(0, 56, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 1)
            };
            vertex_declaration = new VertexDeclaration(graphics_device_manager.GraphicsDevice, elements);
        }

        private VertexDeclaration vertex_declaration = null;

        // hardware mesh buffer manager
        private HardwareMeshBufferManager hardware_mesh_buffer_manager = new HardwareMeshBufferManager();
        internal HardwareMeshBufferManager HardwareMeshBufferManager { get { return hardware_mesh_buffer_manager; } }

    }
}
